﻿/* -----------------------------------------------------------------------------
 * Copyright (c) 2019-2021 linling
 *
 * 本软件"按原样"提供，没有任何明示或暗示的保证。 在任何情况下，作者均
 * 不对因使用本软件而导致的任何损害承担责任。 允许任何人将本软件用于
 * 任何目的（包括商业应用程序），并对其进行更改并自由重新分发，但受以下限制：
 *
 * 1.不得歪曲本软件的来源; 你不能声称你写了原始软件。
 *   如果你在产品中使用此软件，在产品文档中的承认将不胜感激，但不是必需的。
 *
 * 2.更改的源版本必须明确标记，不得歪曲为原始软件。
 *
 * 3.不得从任何来源分发中删除或更改此通知。
 */
#ifndef __MODBUS_TCP_H
#define __MODBUS_TCP_H

#include "mbed.h"
#include "xos2.h" 
#include "api.h" 
#include "net.h" 
#include "qSocket.hpp"
#include <vector>

/// <summary>
/// 一个 ModbusTcpClinet ,可用于发起与PLC(ModbusTcpServer服务端)通讯,读写PLC的数据
/// </summary>
class ModbusTcp {
public:
	/// <summary>
	/// 错误定义
	/// </summary>
	enum ModbusTcpErrorCode {
		/// <summary>
		/// 未连接
		/// </summary>
		NotConnected = -1,
		/// <summary>
		/// 接收超时
		/// </summary>
		ReceiveTimeout = -2,
		/// <summary>
		/// 应答错误
		/// </summary>
		AnswerError = -3,
		/// <summary>
		/// 数据包长度错误
		/// </summary>
		LengthError = -4,
		/// <summary>
		/// 功能错误,根据error_code进一步明确,1=非法功能,2=非法地址,3=非法数据值
		/// </summary>
		FunctionError = -5
	};

	// 开启或者关闭错误时的调试信息。
	bool Debug;
	// 指定下次命令的RTU节点地址
	byte NodeAddress;
	// 最近一次的错误功能码
	byte error_fn;
	// 最近一次的错误代码
	byte error_code;

	ModbusTcp();
	virtual ~ModbusTcp();

	/// <summary>
	/// 初始化连接信息
	/// </summary>
	/// <param name="ip">服务端IP地址</param>
	/// <param name="port">服务端端口号</param>
	/// <param name="nodeaddr">节点地址</param>
	/// <returns>0=设置成功，-1=连接中不接受修改</returns>
	int Init(xapi::string ip, int port, byte nodeaddr);
	/// <summary>
	/// 开始异步连接，具有超时控制
	/// </summary>
	/// <param name="msTimeout">指定超时时间</param>
	/// <returns>0=连接成功，其他值表示错误代码</returns>
	int Connect(int msTimeout = 2000);
	/// <summary>
	/// 关闭连接
	/// </summary>
	void Disconnect();
	/// <summary>
	/// 获取连接状态
	/// </summary>
	/// <returns>true:连接的,false:未连接</returns>
	bool Connected();

	/// <summary>
	/// 发起连续读保持寄存器命令,FN=3
	/// </summary>
	/// <param name="regaddr">寄存器地址</param>
	/// <param name="cnt">要读取的寄存器数量,不是字节数,最大125</param>
	/// <param name="rbuf">数据存放地址</param>
	/// <param name="msTimeout">超时时间,毫秒</param>
	/// <returns>0=操作成功,数据存放在rbuf,且已转换为小端格式,返回其他值表示错误代码</returns>
	int Read(int regaddr, int cnt, uint16* rbuf, int msTimeout = 2000);
	/// <summary>
	/// 发起单次写保持寄存器命令,FN=6
	/// </summary>
	/// <param name="regaddr">寄存器地址</param>
	/// <param name="val">要写入的值,小端格式值</param>
	/// <param name="msTimeout">超时时间,毫秒</param>
	/// <returns>0=操作成功,返回其他值表示错误代码</returns>
	int Write(int regaddr, uint16 val, int msTimeout = 2000);
	/// <summary>
	/// 发起连续写保持寄存器命令,FN=16
	/// </summary>
	/// <param name="regaddr">寄存器地址</param>
	/// <param name="cnt">要连续写入的寄存器数量,不是字节数,最大123</param>
	/// <param name="wbuf">要写入的数据,小端格式</param>
	/// <param name="msTimeout">超时时间,毫秒</param>
	/// <returns>0=操作成功,返回其他值表示错误代码</returns>
	int Write(int regaddr, int cnt, uint16* wbuf, int msTimeout = 2000);

	/// <summary>
	/// 读保持寄存器
	/// </summary>
	/// <param name="regaddr">寄存器地址</param>
	/// <param name="cnt">要读取的寄存器数量,不是字节数,没有最大125的限制</param>
	/// <param name="rbuf">数据存放地址</param>
	/// <param name="msTimeout">单次命令的超时时间,毫秒</param>
	/// <returns>0=操作成功,数据存放在rbuf,且已转换为小端格式,返回其他值表示错误代码</returns>
	int ReadRegArray(int regaddr, int cnt, uint16* rbuf, int msTimeout = 2000 );
	/// <summary>
	/// 写保持寄存器
	/// </summary>
	/// <param name="regaddr">寄存器地址</param>
	/// <param name="cnt">要连续写入的寄存器数量,不是字节数,没有最大123的限制</param>
	/// <param name="wbuf">要写入的数据,小端格式</param>
	/// <param name="msTimeout">单次命令的超时时间,毫秒</param>
	/// <returns>0=操作成功,返回其他值表示错误代码</returns>
	int WriteRegArray(int regaddr, int cnt, uint16* wbuf, int msTimeout = 2000 );

private:
	struct ModbusEventArg_t {
		int16 tcpid;
		int16 cmd;
		int regAddr;
		int regCnd;
	};
	int WaitRecv(int msTimeout);
	int cheak_connect();

	qSocketClient sock;
	xapi::string serverIpaddress;
	int serverPort;
	ModbusEventArg_t ts;
	TimeLoop t;
	bool connected;
	byte txbuff[256 + 13];
	byte rxbuff[256 + 9];
};

/// <summary>
/// 服务端,侦听指定的端口,可配置为接受多个连接
/// </summary>
class ModbusTcpServer : protected qSocketServer {
public:

	/// <summary>
	/// 构造函数,指定节点地址与服务端口号,注意:会自动启动侦听
	/// </summary>
	/// <param name="_nodeaddr">指定节点地址</param>
	/// <param name="_port">指定服务端口号</param>
	/// <param name="_backlog">可指定最大连接数,默认为1</param>
	ModbusTcpServer(int _nodeaddr, int _port, int _backlog = 1);
	/// <summary>
	/// 初始化可供客户端读写的寄存器
	/// </summary>
	/// <param name="_Regs">指定可供读写的缓冲区</param>
	/// <param name="cnt">指定可供读写的缓冲区个数,不是字节数</param>
	void Init(uint16* _Regs, uint16_t cnt);

	/// <summary>
	/// 应周期调用的任务,响应客户端的请求
	/// </summary>
	void Task();

	/// <summary>
	/// 存储可供读取的地址区间,高16位表示起始地址,低16位表示结束地址,默认全范围
	/// </summary>
	std::vector<uint32_t> readableList;
	/// <summary>
	/// 存储可供写入的地址区间,高16位表示起始地址,低16位表示结束地址,默认全范围
	/// </summary>
	std::vector<uint32_t> writableList;

private:
	uint16* Regs;
	int RegsCount;
	int NodeAddress;

	byte txbuff[256 + 13];
	byte rxbuff[256 + 9];
	TimeLoop t;

	bool Readable(int regaddr, int regcnt);
	bool Writable(int regaddr, int regcnt);
};
#endif
